﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Input;
using Foundation;
using Poet.Commands;
using Poet.Views;

namespace Poet.ViewModels
{
    /// <summary>
    /// Search view model.
    /// </summary>
    internal class SearchViewModel : ViewModel, IDataErrorInfo
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="SearchViewModel"/> class.
        /// </summary>
        /// <param name="mainViewModel">The main view model.</param>
        public SearchViewModel(MainViewModel mainViewModel)
        {
            MainViewModel = mainViewModel;
            Find = new RoutedUICommand();
            CommandBindings.Add(new CommandBinding(Find, FindExecuted, FindCanExecute));
            RecentTextPatterns = new ObservableCollection<string>();
            Options = new SearchOptionsViewModel();
            LookInViewModel = new LookInViewModel<DocumentView>(MainViewModel.DocumentViews);
            RegexPattern = string.Empty;
            TextPattern = string.Empty;

            MainViewModel.PropertyChanged += (o, e) =>
                {
                    var propertyName = PropertyNameProvider.GetPropertyName(() => MainViewModel.ActiveDocument);
                    if (e.PropertyName != propertyName) return;
                    LookInViewModel.ActiveItem = ActiveDocumentView;
                    RaisePropertyChangedEvent(() => ActiveDocumentView);
                };
        }

        public LookInViewModel<DocumentView> LookInViewModel { get; set; } 

        /// <summary>
        /// Gets or sets the active document view.
        /// </summary>
        /// <value>
        /// The active document view.
        /// </value>
        public DocumentView ActiveDocumentView
        {
            get { return MainViewModel.ActiveDocument as DocumentView; }
            set
            {
                MainViewModel.ActiveDocument = value;
                RaisePropertyChangedEvent(() => ActiveDocumentView);
            }
        }

        /// <summary>
        /// Gets or sets the find.
        /// </summary>
        /// <value>
        /// The find.
        /// </value>
        public ICommand Find { get; protected set; }

        /// <summary>
        /// Gets or sets the recent regex patterns.
        /// </summary>
        /// <value>
        /// The recent regex patterns.
        /// </value>
        public ObservableCollection<string> RecentRegexPatterns { get; set; }

        /// <summary>
        /// Gets or sets the recent text patterns.
        /// </summary>
        /// <value>
        /// The recent text patterns.
        /// </value>
        public ObservableCollection<string> RecentTextPatterns { get; set; }

        /// <summary>
        /// Gets or sets the options.
        /// </summary>
        /// <value>
        /// The options.
        /// </value>
        public SearchOptionsViewModel Options { get; set; }

        /// <summary>
        /// Gets or sets the regex pattern.
        /// </summary>
        /// <value>
        /// The regex pattern.
        /// </value>
        public string RegexPattern
        {
            get { return Get(() => RegexPattern); }
            set { Set(() => RegexPattern, value); }
        }

        /// <summary>
        /// Gets or sets the text pattern.
        /// </summary>
        /// <value>
        /// The text pattern.
        /// </value>
        public string TextPattern
        {
            get { return Get(() => TextPattern); }
            set { Set(() => TextPattern, value); }
        }

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <returns>The error message for the property. The default is an empty string ("").</returns>
        public string this[string columnName]
        {
            get { return IsValid(columnName); }
        }

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
        public string Error
        {
            get { return IsValid(PropertyNameProvider.GetPropertyName(() => RegexPattern)); }
        }

        /// <summary>
        /// Gets or sets the main view model.
        /// </summary>
        /// <value>
        /// The main view model.
        /// </value>
        protected MainViewModel MainViewModel { get; set; }

        private string IsValid(string propertyName)
        {
            if (propertyName == PropertyNameProvider.GetPropertyName(() => RegexPattern))
            {
                try
                {
                    // ReSharper disable ObjectCreationAsStatement
                    new Regex(RegexPattern);
                    // ReSharper restore ObjectCreationAsStatement
                }
                catch (Exception exception)
                {
                    return exception.Message;
                }
            }

            return null;
        }

        private void FindCanExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            if (ActiveDocumentView == null)
                return;

            var directSearch = bool.Parse((string) args.Parameter);
            if (!LookInViewModel.SelectedItems.Contains(ActiveDocumentView))
                LookInViewModel.SelectedItems.Add(ActiveDocumentView);
            var items = LookInViewModel.Items.Where(i => LookInViewModel.SelectedItems.Contains(i)).ToList();
            var command = directSearch
                     ? SearchAndReplaceCommands.FindNext
                     : SearchAndReplaceCommands.FindPrev;
            args.CanExecute = CanExecuteOrExecute(ActiveDocumentView, command, items, directSearch, false);
        }

        private void FindExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            if(ActiveDocumentView == null)
                return;

            var directSearch = bool.Parse((string) args.Parameter);
            var items = LookInViewModel.Items.Where(i => LookInViewModel.SelectedItems.Contains(i)).ToList();
            var command = directSearch
                     ? SearchAndReplaceCommands.FindNext
                     : SearchAndReplaceCommands.FindPrev;
            CanExecuteOrExecute(ActiveDocumentView, command, items, directSearch, true);
        }

        protected bool CanExecuteOrExecute(DocumentView documentView, RoutedUICommand command, IList<DocumentView> items,
                                             bool directSearch, bool executeIfCan)
        {
            if (documentView == null || (!string.IsNullOrEmpty(Error) && Options.UseRegularExpression))
                return false;

            var canExecute = command.CanExecute(this, documentView);

            if (canExecute)
            {
                if (executeIfCan)
                {
                    command.Execute(this, documentView);

                    if (Options.UseRegularExpression)
                    {
                        if (RecentRegexPatterns.Contains(RegexPattern))
                            RecentRegexPatterns.Remove(RegexPattern);
                        RecentRegexPatterns.Insert(0, RegexPattern);
                    }
                    else
                    {
                        if (RecentTextPatterns.Contains(TextPattern))
                            RecentTextPatterns.Remove(TextPattern);
                        RecentTextPatterns.Insert(0, TextPattern);
                    }

                    documentView.Activate();
                }

                return true;
            }

            var isSide = directSearch
                             ? Equals(items.Last(), documentView)
                             : Equals(items.First(), documentView);

            if (items.Count <= 1 || isSide)
                return false;

            var index = items.IndexOf(documentView);
            var prevDocumentView = directSearch
                                       ? items[index + 1]
                                       : items[index - 1];

            return CanExecuteOrExecute(prevDocumentView, command, items, directSearch, executeIfCan);
        }
    }
}